package com.demo.utils;

import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author : YU.TAN
 * @date : 2021/9/27 11:05
 * redisTemplate.opsForList();//操作list
 * redisTemplate.opsForValue();//操作字符串
 * redisTemplate.opsForCluster();//集群时使用
 * redisTemplate.opsForGeo();//地理位置时使用
 * redisTemplate.opsForHash();//操作hash
 * redisTemplate.opsForSet();//操作set
 * redisTemplate.opsForZSet();//操作有序set
 */
@Component
public class RedisUtil {

    @Autowired
    @Qualifier("functionDomainRedisTemplate")
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private RedissonClient redisson;

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间（秒）
     * @return
     */
    public Boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据Key 获取过期时间
     *
     * @param key 键
     * @return 时间（秒） 返回0代表为永久有效
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key 是否存在
     *
     * @param key 键
     * @return true存在 false 不存在
     */
    public Boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个也可以传多个
     */
    public void del(String... key) {
        try {
            if (key != null && key.length > 0) {
                if (key.length == 1) {
                    redisTemplate.delete(key[0]);
                } else {
                    redisTemplate.delete(CollectionUtils.arrayToList(key));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 删除key
     */
    public Boolean delete(final String key) {
        Boolean result = Boolean.FALSE;
        try {
            redisTemplate.delete(key);
            result = Boolean.TRUE;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }


    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存设置
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public Boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存设置，并设置过期时间
     *
     * @param key   键
     * @param value 值
     * @param time  过期时间（秒） 小于0则默认不过期
     * @return true 成功 false 失败
     */
    public Boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     *
     * @param key 键
     * @param num 递增因子
     * @return
     */
    public Long incr(String key, long num) {
        if (num < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, num);
    }

    /**
     * 递减
     *
     * @param key 键
     * @param num 递减因子
     * @return
     */
    public Long decr(String key, long num) {
        if (num < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().decrement(key, num);
    }

    /**
     * 设置key/value值，并且可以设置超时时间。
     */
    public Boolean setKey(final String key, String value, int time, TimeUnit unit) {
        Boolean result = false;

        try {
            redisTemplate.opsForValue().set(key, value, time, unit);
            result = Boolean.TRUE;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 写入缓存
     */
    public Boolean setKey(final String key, String value) {
        Boolean result = false;

        try {
            redisTemplate.opsForValue().set(key, value);
            result = Boolean.TRUE;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 读取缓存
     */
    public Object getKey(final String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键
     * @param item 项 不能为空
     * @return 值
     */
    public Object hGet(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hMGet(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应的多个键值
     * @return
     */
    public Boolean hMSet(String key, Map<Object, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置过期时间
     *
     * @param key  键
     * @param map  对应的键值
     * @param time 过期时间（秒）
     * @return
     */
    public Boolean hMSet(String key, Map<Object, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据，如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return
     */
    public Boolean hSet(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据，如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  过期时间（秒）
     * @return
     */
    public Boolean hSet(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 删除Hash表中的值
     *
     * @param key  键
     * @param item 项。 可以是多个 不能为null
     * @return
     */
    public Long hDel(String key, Object... item) {
        return redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public Boolean hHashKey(String key, Object item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param num  要增加几(大于0)
     * @return
     */
    public double hIncr(String key, Object item, double num) {
        return redisTemplate.opsForHash().increment(key, item, num);
    }


    /**
     * hash递剑 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param num  要减几(大于0)
     * @return
     */
    public double hDecr(String key, Object item, double num) {
        return redisTemplate.opsForHash().increment(key, item, -num);
    }

    // ============================set=============================

    /**
     * 根绝Key获取Set中的所有值
     *
     * @param key
     * @return
     */
    public Set<Object> sGet(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 查看Key中是否有相应的value
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public Boolean sHasValue(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 将数据存入set
     *
     * @param key    键
     * @param values 值
     * @return
     */
    public Long sSet(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * 将数据存入set
     *
     * @param key    键
     * @param time   缓存时间
     * @param values 值
     * @return
     */
    public Long sSet(String key, long time, Object... values) {
        Long result = redisTemplate.opsForSet().add(key, values);
        if (time > 0) {
            expire(key, time);
        }
        return result;
    }

    /**
     * 获取Set长度
     *
     * @param key 键
     * @return
     */
    public Long sGetSetSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 删除key中的value
     *
     * @param key    键
     * @param values 值
     * @return 删除个数
     */
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    // ===============================list=================================

    /**
     * 根据key获取List
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0到-1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 根绝key获取List的长度
     *
     * @param key 键
     * @return
     */
    public Long lGetListSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 通过索引获取List中的值
     *
     * @param key   键
     * @param index 索引 index>0时 0代表表头第一个元素   index<0时 -1 代表最后一个元素， -2 代表倒数第二个
     * @return
     */
    public Object lGetIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 链表右边push
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public Long listRightPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 链表右边push
     *
     * @param key   键
     * @param value 值
     * @param time  超时时间（秒）
     * @return
     */
    public Long listRightPush(String key, Object value, long time) {
        Long result = redisTemplate.opsForList().rightPush(key, value);
        if (time > 0) {
            expire(key, time);
        }
        return result;
    }

    /**
     * 链表右边push
     *
     * @param key    键
     * @param values 值
     * @return
     */
    public Long listRightPushAll(String key, Object... values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    /**
     * 链表右边push
     *
     * @param key    键
     * @param values 值
     * @param time   超时时间（秒）
     * @return
     */
    public Long listRightPushAll(String key, long time, Object... values) {
        Long result = redisTemplate.opsForList().rightPushAll(key, values);
        if (time > 0) {
            expire(key, time);
        }
        return result;
    }

    /**
     * 更新
     *
     * @param key   键
     * @param index 索引位置
     * @param value 值
     */
    public void listUpdateIndex(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 从存储在键中的列表中删除等于值的元素的第一个计数事件。count> 0：删除等于从左到右移动的值的第一个元素；count< 0：删除等于从右到左移动的值的第一个元素；count = 0：删除等于value的所有元素。
     *
     * @param key
     * @param count
     * @param value
     * @return
     */
    public Long listRemove(String key, long count, Object value) {
        return redisTemplate.opsForList().remove(key, count, value);
    }


    /**
     * 如果我们需要一个小型的消息中间件，可以选择redis的订阅/发布来实现。
     */

    public void publish(String channal, Object obj) {
        redisTemplate.convertAndSend(channal, obj);
    }




    /**
     * redis分布式锁1.0
     *
     * @return
     */
    public String getLock() {
        RLock lockKey = redisson.getLock("lockKey");
        try {
            lockKey.lock();
            Integer key = Integer.parseInt(String.valueOf(this.getKey("key")));
            System.out.println(key);
            Integer nowKey = key + 1;
            this.setKey("key", nowKey + "");
            return key + "";
        } finally {
            lockKey.unlock();
        }
    }


    /**
     * redis分布式锁2.0
     *
     * @return
     */
    public String getLockB() {
        String lockKey = "lockKey";
        Integer key = 0;
        String clientId = UUID.randomUUID().toString();
        try {
//      setIfAbsent相当于jedis中的setnx，如果能赋值就返回true，如果已经有值了，就返回false
            Boolean result = this.setKey(lockKey, clientId, 30, TimeUnit.SECONDS);
            if (!result) {
                return "error";
            }
            key = Integer.parseInt(String.valueOf(this.getKey("key")));
            Integer newKey = key + 1;
            this.setKey("key", newKey + "");
            System.out.println(key);
        } finally {
            //释放(判断是否是当前客户加锁)
            if (clientId.equals(this.getKey(lockKey))) {
                this.delete(lockKey);
            }
        }
        return key + "";

    }


    // ============================redisson=============================

    /**
     * 加锁
     *
     * @param lockKey 键
     */
   public void lock(String lockKey) {
        RLock lock = redisson.getLock(lockKey);
        lock.lock();
    }

    /**
     * 释放锁
     *
     * @param lockKey
     */
   public void unLock(String lockKey) {
        RLock lock = redisson.getLock(lockKey);
        lock.unlock();
    }

    /**
     * 加锁并设置过期时间
     *
     * @param lockKey 键
     * @param timeOut 过期时间(秒)
     */
   public void lock(String lockKey, long timeOut) {
        RLock lock = redisson.getLock(lockKey);
        lock.lock(timeOut, TimeUnit.SECONDS);
    }


    /**
     * 加锁并设置过期时间 并指定时间单位
     *
     * @param lockKey  键
     * @param timeOut  过期时间
     * @param timeUnit 过期时间单位
     */
    public void lock(String lockKey, long timeOut, TimeUnit timeUnit) {
        RLock lock = redisson.getLock(lockKey);
        lock.lock(timeOut, timeUnit);
    }


    /**
     * 尝试获取锁  获取到则持有该锁返回true,未获取到立即返回false
     *
     * @param lockKey 键
     * @return true-获取锁成功  false-获取锁失败
     */
   public Boolean tryLock(String lockKey) {
        RLock lock = redisson.getLock(lockKey);
        return lock.tryLock();
    }


    /**
     * 尝试获取锁。 获取到则持有该锁leaseTime时间
     *
     * @param lockKey   键
     * @param waitTime  尝试获取时间
     * @param leaseTime 锁持有时间
     * @param unit      单位
     * @return
     */
    public Boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) {

        RLock lock = redisson.getLock(lockKey);
        try {
            return lock.tryLock(waitTime, leaseTime, unit);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 锁是否被任意一个线程持有
     *
     * @param lockKey 键
     * @return true-被锁 false-未被锁
     */
   public Boolean isLocked(String lockKey) {
        RLock lock = redisson.getLock(lockKey);
        return lock.isLocked();
    }

    /**
     * lock.isHeldByCurrentThread()的作用是查询当前线程是否保持此锁定
     *
     * @param lockKey 键
     * @return
     */
    Boolean isHeldByCurrentThread(String lockKey) {
        RLock lock = redisson.getLock(lockKey);
        return lock.isHeldByCurrentThread();
    }

    /**
     * 添加元素到布隆过滤器中
     *
     * @param bloomFilterName
     * @param value
     */
    void bfAdd(String bloomFilterName, String value) {
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter(bloomFilterName);
        bloomFilter.add(value);
    }

    /**
     * 初始化布隆过滤器
     *
     * @param bloomFilterName bloomFilterName  默认给了expectedInsertions = 1000000L falseProbability = 0.0001
     * @return 布隆过滤器名称
     */
    RBloomFilter<String> init(String bloomFilterName) {
        long expectedInsertions = 1000000L;
        double falseProbability = 0.0001;
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter(bloomFilterName);
        bloomFilter.tryInit(expectedInsertions, falseProbability);
        return bloomFilter;
    }

    /**
     * 初始化布隆过滤器
     *
     * @param bloomFilterName    名称
     * @param expectedInsertions 预计插入元素总量
     * @param falseProbability   精度（误判率）
     * @return 布隆过滤器名称
     */
    RBloomFilter<String> init(String bloomFilterName, Long expectedInsertions, Double falseProbability) {
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter(bloomFilterName);
        bloomFilter.tryInit(expectedInsertions, falseProbability);
        return bloomFilter;
    }

    /**
     * 删除布隆过滤器（注意不是删除元素）
     *
     * @param bloomFilterName 名称
     */
    void bfDelete(String bloomFilterName) {
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter(bloomFilterName);
        bloomFilter.delete();
    }

    /**
     * 判断
     *
     * @param bloomFilterName 名称
     * @param value           false表示则一定不存在，true 则可能存在
     * @return
     */
    Boolean bfContains(String bloomFilterName, String value) {
        RBloomFilter<String> bloomFilter = redisson.getBloomFilter(bloomFilterName);
        return bloomFilter.contains(value);
    }
}
